﻿#define ONLINE

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.ServiceModel;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
using ClientCommonWpf;
using Director.DomainModels;
using Xceed.Wpf.AvalonDock;
using Timer = System.Threading.Timer;

namespace Director.ViewModels
{
	[CallbackBehavior(ConcurrencyMode = ConcurrencyMode.Single, UseSynchronizationContext = false)]
	class MainVM : AbstractViewModel, IDirectorServiceCallback
	{
		public const string DOC_CONTENTID_SEPARATOR = ";";
		private readonly MainWindow _mainWindow;
		private readonly IDirectorService _client;
		private DockingManager _dockManager;
		private readonly DispatcherTimer _timer;
		Timer _candleTimer;

		private readonly Dictionary<Security, Dictionary<TimeFrame,Candle>> _lastCandles = new Dictionary<Security, Dictionary<TimeFrame, Candle>>();
		private readonly Dictionary<Security, List<TimeFrame>> _received = new Dictionary<Security, List<TimeFrame>>();
		private readonly List<AbstractPaneVM> _receivedPanes = new List<AbstractPaneVM>();

		public SynchronizationContext UiSyncContext { get; private set; }

		private void PingCallback(object sender, EventArgs eventArgs)
		{
			_client.Ping();
		}

		private void TimerCallback(object state)
		{
			_receivedPanes.Clear();
			_received.Clear();

			if (TransacState != EServiceState.Connected) return;

			var pairs = Docs.OfType<AbstractPaneVM>().Concat(Tools).SelectMany(e => e.ListenSecurities.Select(p=>new KeyValuePair<Security,TimeFrame>(p,e.ListenTimeFrame))).ToArray();
			pairs = pairs.Distinct().ToArray();
			foreach (var pair in pairs)
			{
				var c = GetLastCandle(pair.Key, pair.Value);
				if (c==null || c.Date.AddSeconds(pair.Value.TimeFrameLength) <= DateTime.Now)
				{
					GetHistory(pair.Key, pair.Value);
				}
			}
		}

		public void ListenCandles(Security security, TimeFrame timeFrame)
		{
			GetLastCandle(security, timeFrame);
		}

		private Candle GetLastCandle(Security security, TimeFrame timeFrame)
		{
			Dictionary<TimeFrame, Candle> candles;
			if (!_lastCandles.TryGetValue(security, out candles))
			{
				_lastCandles[security] = candles = new Dictionary<TimeFrame, Candle>();
			}
			Candle candle;
			if (!candles.TryGetValue(timeFrame, out candle))
			{
				candles[timeFrame] = null;
			}
			return candle;
		}

		public ObservableCollection<OutputItem> LogCollection { get; private set; }

		/// <summary>
		/// фиктивное объявление
		/// </summary>
		public AbstractPaneVM Model { get; set; }
		
		public MainVM(MainWindow mainWindow)
		{
			TransacState = EServiceState.Disconnected;
			_mainWindow = mainWindow;
			Tools = new ObservableCollection<AbstractToolVM>();
			Docs = new ObservableCollection<AbstractDocVM>();
			LogCollection = new ObservableCollection<OutputItem>();
			var pipeFactory = new DuplexChannelFactory<IDirectorService>(this, new NetNamedPipeBinding { MaxReceivedMessageSize = 2147483647, MaxBufferPoolSize = 2147483647}, new EndpointAddress("net.pipe://localhost/txml-pipe"));
			_client = pipeFactory.CreateChannel();
			UiSyncContext = SynchronizationContext.Current;

			SecuritiesCommand = new RelayCommand(ExecuteSecuritiesCommand);
			TopCommand = new RelayCommand(ExecuteTopCommand);
			OutputCommand = new RelayCommand(ExecuteOutputCommand);
			PositionsCommand = new RelayCommand(ExecutePositionsCommand);
			ConnectCommand = new RelayCommand(ExecuteConnectCommand);
			DicconnectCommand = new RelayCommand(ExecuteDicconnectCommand);

			LeaderCommand = new RelayCommand(ExecuteLeaderCommand);

			_timer = new DispatcherTimer(TimeSpan.FromMinutes(1), DispatcherPriority.ApplicationIdle, PingCallback, mainWindow.Dispatcher);
			_timer.Start();


			
		}

		public void Init(DockingManager dockManager)
		{
			_dockManager = dockManager;
#if ONLINE
			StateChanged(_client.Hello());
#endif
		}

		#region commands

		public RelayCommand LeaderCommand { get; private set; }

		private void ExecuteLeaderCommand(object obj)
		{
			OpenDoc(new LeaderStrategyVM(this));
		}

		public RelayCommand OutputCommand { get; private set; }

		private void ExecuteOutputCommand(object obj)
		{
			OpenTool(EToolId.OUTPUT);
		}

		public RelayCommand PositionsCommand { get; private set; }

		private void ExecutePositionsCommand(object obj)
		{
			OpenTool(EToolId.POSITIONS);
		}

		public RelayCommand SecuritiesCommand { get; private set; }

		private void ExecuteSecuritiesCommand(object obj)
		{
			OpenTool(EToolId.SECURITIES);
		}

		public RelayCommand TopCommand { get; private set; }

		private void ExecuteTopCommand(object obj)
		{
			OpenTool(EToolId.TOP);
		}

		public RelayCommand DicconnectCommand { get; private set; }

		private void ExecuteDicconnectCommand(object obj)
		{
			_client.Disconnect();
		}

		public RelayCommand ConnectCommand { get; private set; }

		private void ExecuteConnectCommand(object obj)
		{
			_client.Connect();
		}

		#endregion
		
		public Candle[] GetCandles(Security security, TimeFrame timeFrame, DateTime? from = null)
		{
			var candles = (Candle[])_client.GetCandles(security.Id, timeFrame.Id, from ?? DateTime.MinValue);
			if (candles.Length > 0)
			{
				var candle = candles[candles.Length - 1];
				SetLastCandle(security, timeFrame, candle);
			}

			UiSyncContext.Post(state => UpdatePositions(), null);

			return candles;
		}

		private void SetLastCandle(Security security, TimeFrame timeFrame, Candle candle)
		{
			var existsCandle = GetLastCandle(security, timeFrame);

			if (existsCandle == null || existsCandle.Date < candle.Date)
			{
				_lastCandles[security][timeFrame] = candle;
			}
		}

		#region IDirectorServiceCallback

		public void ServiceStateChanged(EServiceState newState)
		{
			UiSyncContext.Post(state => StateChanged((EServiceState)state), newState);
		}

		public void OnServiceError(string errorMessage)
		{
			UiSyncContext.Post(o => LogCollection.Add(new OutputItem(EOutputItemType.ServiceError, errorMessage)), null);
		}

		public void OnServiceLog(string logMessage)
		{
			UiSyncContext.Post(o => LogCollection.Add(new OutputItem(EOutputItemType.ServiceLog, logMessage)), null);
		}

		public void OnNewTrade(Trade trade)
		{
			DataManager.Instance.DirectorStorage.WriteTrade(trade);
			UpdatePositions();
		}

		public void OnNewOrder(Order order)
		{
			DataManager.Instance.DirectorStorage.WriteOrder(order);
			OrdersRegistrator.Instance.RegisterOrder(order);
		}

		public void CandlesUpdated(int securityId, int timeFrameLength)
		{
			UiSyncContext.Post(o =>
			{
				var timeFrame = DataManager.Instance.TimeFrames.Single(e => e.TimeFrameLength == timeFrameLength);

				List<TimeFrame> list;
				if (!_received.TryGetValue(DataManager.Instance.Securities[securityId], out list))
				{
					_received.Add(DataManager.Instance.Securities[securityId], list = new List<TimeFrame>());
				}
				if (!list.Contains(timeFrame))
				{
					list.Add(timeFrame);
				}
				else
				{
					//throw new Exception("Странно!");
				}

				var panes = Docs.OfType<AbstractPaneVM>().Concat(Tools).Except(_receivedPanes).ToArray();

				foreach (var abstractPaneVM in panes)
				{
					var allFound = true;
					var listenSecurities = abstractPaneVM.ListenSecurities.ToArray();
					if (listenSecurities.Length == 0)
					{
						_receivedPanes.Add(abstractPaneVM);
						continue;
					}
					foreach (var security in listenSecurities)
					{
						if (allFound && timeFrame != abstractPaneVM.ListenTimeFrame || !_received.ContainsKey(security))
						{
							allFound = false;
						}
					}

					if (allFound)
					{
						_receivedPanes.Add(abstractPaneVM);
						abstractPaneVM.CandlesUpdated();
					}
				}
			}, null);
		}

		public void DataReceived(EDataKind kind, int id)
		{
			switch (kind)
			{
				case EDataKind.Positions:
					UpdatePositions();
					break;
				default:
					throw new ArgumentOutOfRangeException("kind");
			}
		}

		private void UpdatePositions()
		{
			UiSyncContext.Post(o =>
			{
				DataManager.Instance.Positions = _client.GetPositions().OrderBy(e => e.Name).ToArray();
				var positionsVM = Tools.OfType<PositionsVM>().FirstOrDefault();
				if (positionsVM != null)
				{
					positionsVM.Update();
				}
			}, null);
		}

		private void StateChanged(EServiceState newState)
		{
			if (TransacState == newState) return;

			TransacState = newState;
			OnPropertyChanged(() => TransacState);

			if (newState==EServiceState.Connected)
			{
				DataManager.Instance.Boards = _client.GetBoards().OrderBy(e => e.Name).ToArray();
				DataManager.Instance.Positions = _client.GetPositions().OrderBy(e => e.Name).ToArray();
				DataManager.Instance.TimeFrames = _client.GetTimeFrames().OrderBy(e => e.Length).ToArray();
				DataManager.Instance.Securities = _client.GetSecurities().OrderBy(e => e.Code).ToDictionary(s=>s.Id,s=>s);
				DataManager.Instance.Markets = _client.GetMarkets().OrderBy(e => e.Name).ToArray();

				_mainWindow.Connected();

				//foreach (var sharesByCode in DataManager.Instance.SharesByCodes)
				//{
				//	ListenCandles(sharesByCode, DataManager.Instance[TimeFrame.ELength.M5]);
				//}
				if (_candleTimer == null)
				{
					_candleTimer = new Timer(TimerCallback, null, TimeSpan.FromSeconds(61 - DateTime.Now.Second), TimeSpan.FromMinutes(1));
				}
				TimerCallback(null);
			}
			if (newState == EServiceState.Disconnected)
			{
				_mainWindow.Disconnected();
				Docs.Clear();
				Tools.Clear();
			}
			
			ShowBalloon("State", newState.ToString());
		}

		public EServiceState TransacState { get; set; }
		
		public void GetHistory(Security security, TimeFrame timeFrame)
		{
			Task.Run(() => _client.RequestHistoryData(security.Board, security.Code, timeFrame.Id, false));
		}

		public bool Buy(Security security, int quantity, string source)
		{
			ShowBalloon("Buy", security.Code + " : " + quantity);
			var id = _client.Buy(security.Id, quantity);
			if (id != 0)
			{
				DataManager.Instance.DirectorStorage.RegisterOrder(id, source);
			}
			return id != 0;
		}

		public bool Sell(Security security, int quantity, string source)
		{
			ShowBalloon("Sell", security.Code + " : " + quantity);

			var id = _client.Sell(security.Id, quantity);
			if (id != 0) DataManager.Instance.DirectorStorage.RegisterOrder(id, source);
			return id != 0;
		}


		#endregion

		public void OpenTool(EToolId toolId)
		{
			var vm = GetToolVM(toolId);
			if (Tools.All(e => e.ContentId != vm.ContentId))
			{
				try
				{
					vm.Init();
				}
				catch (Exception ex)
				{
					this.OnServiceError(ex.Message);
					return;
				}
				Tools.Add(vm);
			}
			else
			{
				var tvm = Tools.First(e => e.ContentId == vm.ContentId);
				Tools.Remove(tvm);
				Tools.Add(tvm);
			}
		}

		public void OpenDoc(AbstractDocVM vm)
		{
			var exists = Docs.FirstOrDefault(e => e.ContentId == vm.ContentId);
			if (exists == null)
			{
				vm.Init();
				Docs.Add(vm);
				_dockManager.ActiveContent = vm;
			}
			else
			{
				exists.IsActive = true;
				_dockManager.ActiveContent = exists;
			}
		}

		public AbstractToolVM GetToolVM(EToolId toolId)
		{
			var tool = Tools.FirstOrDefault(e => e.ToolId == toolId);
			if (tool != null)
			{
				return tool;
			}

			switch (toolId)
			{
				case EToolId.SECURITIES:
					return new SecuritiesVM(this);
				case EToolId.OUTPUT:
					return new OutputVM(this, LogCollection);
				case EToolId.POSITIONS:
					return new PositionsVM(this);
				case EToolId.TOP:
					return new TopVM(this);
				default:
					throw new ArgumentOutOfRangeException("toolId");
			}
		}

		private AbstractDocVM GetDocVM(EDocId edocId, string s)
		{
			switch (edocId)
			{
				case EDocId.EMPTY_DOC:
					return new EmptyDocVM(this);
				case EDocId.CHART:
					return new ChartVM(this,s);
				case EDocId.LEADER:
					return new LeaderStrategyVM(this);
				default:
					throw new ArgumentOutOfRangeException("edocId");
			}
		}

		public AbstractPaneVM GetPaneVM(string contentId)
		{
			EToolId toolId;
			if (Enum.TryParse(contentId, out toolId))
			{
				var tvm = GetToolVM(toolId);
				Tools.Add(tvm);
				return tvm;
			}
			var docId = contentId.Split(new[] { DOC_CONTENTID_SEPARATOR },StringSplitOptions.None);
			EDocId edocId;
			if (Enum.TryParse(docId[0], out edocId))
			{
				var dvm = GetDocVM(edocId, docId[1]);
				Docs.Add(dvm);
				return dvm;
			}
			return null;
		}

		public override void Dispose()
		{
			if (_candleTimer != null)
			{
				_candleTimer.Dispose();
			}

			_timer.Stop();
#if ONLINE
			_client.Bye();
#endif
			base.Dispose();
		}


		public ObservableCollection<AbstractToolVM> Tools { get; private set; }

		public ObservableCollection<AbstractDocVM> Docs { get; private set; }

		public void CloseDoc(AbstractDocVM abstractDocVM)
		{
			Docs.Remove(abstractDocVM);
			abstractDocVM.Dispose();
		}

		public void CloseTool(AbstractToolVM abstractToolVM)
		{
			Tools.Remove(abstractToolVM);
			abstractToolVM.Dispose();
		}

		public void ToolClosed(AbstractToolVM abstractToolVM)
		{
			Tools.Remove(abstractToolVM);
			abstractToolVM.Dispose();
		}

		public void SubscribeSecurutues(Security[] securities)
		{
			Task.Run(() => _client.Subscribe(securities));
		}

		public decimal GetCurrentPrice(Security security)
		{
			Dictionary<TimeFrame, Candle> candles;
			if (!_lastCandles.TryGetValue(security, out candles))
			{
				_lastCandles[security] = candles = new Dictionary<TimeFrame, Candle>();
			}
			Candle candle = candles.Select(e => e.Value).Where(c=>c!=null).OrderByDescending(e => e.Date).FirstOrDefault();
			if (candle!=null)
			{
				return candle.Close;
			}
			return 0m;
		}

		public SecCandle[] GetLastCandles(IEnumerable<Security> securities, TimeFrame listenTimeFrame)
		{
			var result = GetLastCandles(securities, listenTimeFrame, null);
			foreach (var secCandle in result)
			{
				SetLastCandle(secCandle.Security, listenTimeFrame, secCandle.Candle);
			}
			return result;
		}

		public SecCandle[] GetLastCandles(IEnumerable<Security> securities, TimeFrame listenTimeFrame, DateTime? lastOnDate = null)
		{
			var result = new List<SecCandle>();
			foreach (var security in securities)
			{
				var cnd = _client.GetLastCandle(security.Id, listenTimeFrame.Id, lastOnDate==null?DateTime.Now : lastOnDate.Value);
				if (cnd == null) continue;
				result.Add(new SecCandle(security, cnd));
			}
			return result.ToArray();
		}

		public void UpdateTitleInfo(decimal? pnl)
		{
			UpdateNotify(String.Format("PNL: {0:N}", pnl));
			Title = "TMNTS " + pnl.GetValueOrDefault().ToString("N");
			OnPropertyChanged(()=>Title);
		}

		public string Title { get; private set; }

		public Window View { get; set; }
		public Action<string> UpdateNotify { get; set; }
		public Action<string, string> ShowBalloon { get; set; }
	}
}
